package com.starview.star.compiler.transform;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.starview.platform.data.IValue;
import com.starview.platform.data.type.ContractConstraint;
import com.starview.platform.data.type.FieldConstraint;
import com.starview.platform.data.type.FieldTypeConstraint;
import com.starview.platform.data.type.Freshen;
import com.starview.platform.data.type.IType;
import com.starview.platform.data.type.ITypeConstraint;
import com.starview.platform.data.type.Location;
import com.starview.platform.data.type.Refresher;
import com.starview.platform.data.type.Subsume;
import com.starview.platform.data.type.Type;
import com.starview.platform.data.type.TypeConstraintException;
import com.starview.platform.data.type.TypeExp;
import com.starview.platform.data.type.TypeUtils;
import com.starview.platform.data.type.TypeVar;
import com.starview.platform.data.type.UniversalType;
import com.starview.star.compiler.CompilerUtils;
import com.starview.star.compiler.ErrorReport;
import com.starview.star.compiler.FreeVariables;
import com.starview.star.compiler.ast.Abstract;
import com.starview.star.compiler.ast.Apply;
import com.starview.star.compiler.ast.IAbstract;
import com.starview.star.compiler.ast.Name;
import com.starview.star.compiler.canonical.DefaultTransformer;
import com.starview.star.compiler.canonical.FunctionLiteral;
import com.starview.star.compiler.canonical.IContentAction;
import com.starview.star.compiler.canonical.IContentExpression;
import com.starview.star.compiler.canonical.IContentPattern;
import com.starview.star.compiler.canonical.IStatement;
import com.starview.star.compiler.canonical.MemoExp;
import com.starview.star.compiler.canonical.Overloaded;
import com.starview.star.compiler.canonical.OverloadedVariable;
import com.starview.star.compiler.canonical.PatternAbstraction;
import com.starview.star.compiler.canonical.Resolved;
import com.starview.star.compiler.canonical.VarDeclaration;
import com.starview.star.compiler.canonical.VarEntry;
import com.starview.star.compiler.canonical.Variable;
import com.starview.star.compiler.standard.StandardNames;
import com.starview.star.compiler.type.DictInfo;
import com.starview.star.compiler.type.Dictionary;
import com.starview.star.compiler.type.TypeContracts;
import com.starview.star.compiler.type.Visibility;
import com.starview.star.compiler.util.AccessMode;
import com.starview.star.compiler.util.UndoableHash;
import com.starview.star.compiler.util.UndoableMap;

/**
 * Refactored overloader using the transformation pattern
 * 
 * @author fgm
 * 
 */
public class Over extends DefaultTransformer<OverContext>
{
  private final UndoableMap<String, IContentExpression> substitutions = new UndoableHash<String, IContentExpression>();

  @Override
  public IContentExpression transformFunctionLiteral(FunctionLiteral fun, OverContext context)
  {
    int mark = context.markDict();
    try {
      IType funType = fun.getType();
      if (TypeUtils.isOverloadedType(funType)) {
        IContentPattern args[] = fun.getArgs();
        for (IContentPattern arg : args) {
          if (arg instanceof Variable)
            context.define(arg.getType(), (IContentExpression) arg);
        }
      }

      IContentPattern trArgs[] = transformPatterns(fun.getArgs(), context);
      IContentExpression trRes = fun.getBody().transform(this, context);

      Variable[] freeVars = FreeVariables.freeFreeVars(trArgs, trRes, context.getLocalCxt());

      String funName = fun.getName();
      IContentExpression newName = substitute(funName);
      if (newName != null) {
        funName = ((Variable) newName).getName();
        funType = newName.getType();
      }

      return new FunctionLiteral(fun.getLoc(), funName, funType, trArgs, trRes, freeVars);
    } finally {
      context.resetDict(mark);
    }
  }

  @Override
  public IContentExpression transformPatternAbstraction(PatternAbstraction ptn, OverContext context)
  {
    int mark = context.markDict();
    try {
      if (TypeUtils.isOverloadedType(ptn.getType())) {
        IContentPattern args[] = ptn.getArgs();
        for (IContentPattern arg : args) {
          if (arg instanceof Variable)
            context.define(arg.getType(), (IContentExpression) arg);
        }
      }

      String ptnName = ptn.getName();
      IContentExpression newName = substitute(ptnName);
      if (newName != null)
        ptnName = ((Variable) newName).getName();

      IContentPattern trMatch = ptn.getMatch().transformPattern(this, context);
      IContentExpression trRes = ptn.getResult().transform(this, context);

      Variable[] freeVars = FreeVariables.freeFreeVars(new IContentPattern[] { trMatch }, trRes, context.getLocalCxt());

      return new PatternAbstraction(ptn.getLoc(), ptnName, ptn.getType(), trMatch, trRes, freeVars);
    } finally {
      context.resetDict(mark);
    }
  }

  @Override
  public IContentExpression transformOverloaded(Overloaded over, OverContext context)
  {
    IContentExpression inner = over.getInner().transform(this, context);

    Location loc = over.getLoc();

    IType overType = over.getDictType();

    IType reqs[] = TypeUtils.getOverloadRequirements(overType);
    IContentExpression args[] = new IContentExpression[reqs.length];

    for (int ix = 0; ix < args.length; ix++) {
      IType reqType = reqs[ix];

      assert reqType instanceof TypeExp;

      args[ix] = resolve(loc, reqType, context);

      if (args[ix] == null)
        return over; // not ready to overload, signal for overloading later
    }

    if (inner instanceof Variable)
      inner = ((Variable) inner).underLoad(); // convert an overloaded variable to a regular one.

    return new Resolved(loc, TypeUtils.getOverloadedType(overType), overType, inner, args);
  }

  @Override
  public IContentExpression transformResolved(Resolved res, OverContext context)
  {
    // TODO Auto-generated method stub
    return super.transformResolved(res, context);
  }

  @Override
  public IContentExpression transformVariable(Variable var, OverContext context)
  {
    // TODO Auto-generated method stub
    return super.transformVariable(var, context);
  }

  @Override
  public IContentAction transformVarDeclaration(VarDeclaration var, OverContext context)
  {
    return new VarDeclaration(var.getLoc(), var.getPattern().transformPattern(this, context), var.isReadOnly(), var
        .getValue().transform(this, context));
  }

  @Override
  public IStatement transformVarEntry(VarEntry entry, OverContext context)
  {
    final IContentExpression transformedValue = entry.getValue().transform(this, context);

    return new VarEntry(entry.getDefined(), entry.getLoc(), entry.getVarPattern().transformPattern(this, context),
        transformedValue, entry.isReadOnly(), entry.getVisibility());
  }

  public IContentPattern[] transformPatterns(IContentPattern[] args, OverContext cxt)
  {
    IContentPattern nArgs[] = new IContentPattern[args.length];
    for (int ix = 0; ix < args.length; ix++)
      nArgs[ix] = args[ix].transformPattern(this, cxt);
    return nArgs;
  }

  private IContentExpression resolve(Location loc, IType type, OverContext context)
  {
    type = TypeUtils.deRef(type);

    IContentExpression dictVar = context.locateDictVar(type);
    if (dictVar != null)
      return dictVar;
    else if ((type instanceof TypeExp || type instanceof Type)) {
      String instanceName = instanceFunName(type);
      if (context.isDefinedVar(instanceName))
        return checkInstance(loc, type, instanceName, context);
      else if (TypeUtils.isGroundSurface(type) || (TypeUtils.isVarSurface(type) && !context.varsInScope(type))) {
        instanceName = TypeContracts.contractFallbackName(type);
        if (context.isDefinedVar(instanceName))
          return checkInstance(loc, type, instanceName, context);
      }
    }
    return null;
  }

  private IContentExpression checkInstance(Location loc, IType type, String instanceName, OverContext cxt)
  {
    Dictionary localCxt = cxt.getLocalCxt();
    DictInfo instance = localCxt.varReference(instanceName);
    // If the instance is not null but has a variable type, then it is a
    // marker
    // declaration; not the final one. So we do not resolve for it yet.
    if (instance != null && !TypeUtils.isTypeVar(instance.getType())) {
      IType instanceType = instance.getType();
      IType instType = TypeUtils.isOverloadedType(instanceType) ? TypeUtils.refreshOverloaded(instanceType) : Freshen
          .freshenForUse(instanceType);

      if (TypeUtils.isOverloadedType(instType)) {
        IType instArgs[] = TypeUtils.getOverloadRequirements(instType);
        IType conBoundType = TypeUtils.getOverloadedType(instType);

        try {
          Subsume.same(type, conBoundType, loc, localCxt);
        } catch (TypeConstraintException e) {
          cxt.getErrors().reportError("cannot resolve: " + type + "\nbecause " + e.getMessage(),
              Location.merge(loc, e.getLocs()));
          return null;
        }

        IContentExpression args[] = new IContentExpression[instArgs.length];
        for (int ix = 0; ix < args.length; ix++) {
          assert TypeUtils.deRef(instArgs[ix]) instanceof TypeExp;
          TypeExp argType = (TypeExp) instArgs[ix];
          IContentExpression cVar = resolve(loc, argType, cxt);

          if (cVar == null)
            cVar = cxt.locateDictVar(argType);
          if (cVar == null)
            return null;

          args[ix] = cVar;
        }

        IContentExpression over = instance.getVariable();
        if (over instanceof OverloadedVariable)
          over = new Variable(over.getLoc(), instType, ((OverloadedVariable) over).getName());
        return new Resolved(loc, type, instType, over, args);
      } else
        return instance.getVariable();
    }
    return null;
  }

  public static VarEntry constructInstanceFunction(Location loc, String funName, IType type, TypeExp implType,
      IContentExpression implementation, Dictionary dict, ErrorReport errors, Visibility visibility)
  {
    OverContext context = new OverContext(dict, errors, 0);
    Over over = new Over();

    IType genImplType = Refresher.generalize(implType, dict);

    int varNo = 0;

    IType dictType = computeDictionaryType(genImplType, loc, AccessMode.readWrite);

    try {
      Subsume.same(TypeUtils.getOverloadedType(dictType), implType, loc, dict);
    } catch (TypeConstraintException e) {
      errors.reportError("(internal) problem in unifying type for overloading: " + e.getMessage(), Location.merge(loc,
          e.getLocs()));
    }

    IType reqTypes[] = TypeUtils.getOverloadRequirements(dictType);

    if (reqTypes.length > 0) {
      Variable reqVars[] = new Variable[reqTypes.length];
      int currDictState = context.markDict();

      for (int ix = 0; ix < reqTypes.length; ix++) {
        TypeExp reqType = (TypeExp) reqTypes[ix];
        Variable dictVar = reqVars[ix] = new Variable(loc, reqTypes[ix], reqType.typeLabel() + varNo++);
        dict.declareVar(dictVar.getName(), dictVar, AccessMode.readOnly, visibility, true);
        context.define(reqTypes[ix], (IContentExpression) dictVar);
      }

      IContentExpression transformed = implementation.transform(over, context);

      context.resetDict(currDictState);

      // IType instFunType = Refresher.generalize(TypeUtils.type(reqTypes,
      // TypeUtils.getOverloadedType(dictType)));
      Variable iFree[] = FreeVariables.findFreeVars(transformed, dict);
      FunctionLiteral instFun = new FunctionLiteral(loc, funName, dictType, reqVars, transformed, iFree);

      return new VarEntry(loc, new Variable(loc, dictType, funName), instFun, AccessMode.readOnly, visibility);
    } else {
      implementation = implementation.transform(over, context);

      Variable[] freeVars = FreeVariables.findFreeVars(implementation, dict);
      implementation = new MemoExp(loc, implementation, freeVars);
      return new VarEntry(loc, new Variable(loc, dictType, funName), implementation, AccessMode.readOnly, visibility);
    }
  }

  public static VarEntry instanceFunction(Location loc, String funName, IType type, TypeExp implType,
      IContentExpression implementation, Dictionary dict, ErrorReport errors, Visibility visibility)
  {
    IType genImplType = Refresher.generalize(implType, dict);
    IType dictType = computeDictionaryType(genImplType, loc, AccessMode.readOnly);

    Dictionary instDict = dict.fork();
    OverContext context = new OverContext(dict, errors, 0);
    Over over = new Over();

    int varNo = 0;

    IType[] reqTypes = TypeUtils.getOverloadRequirements(dictType);
    Variable reqVars[] = new Variable[reqTypes.length];

    IType instArgTypes[] = new IType[reqTypes.length];

    for (int ix = 0; ix < reqTypes.length; ix++) {
      IType reqType = instArgTypes[ix] = reqTypes[ix];

      Variable dictVar = reqVars[ix] = new Variable(loc, reqType, reqType.typeLabel() + varNo++);
      instDict.declareVar(dictVar.getName(), dictVar, AccessMode.readOnly, Visibility.priVate, true);
      context.define(reqType, (IContentExpression) dictVar);
    }

    implementation = implementation.transform(over, context);

    if (reqTypes.length > 0) {
      Variable[] iFree = FreeVariables.freeFreeVars(reqVars, implementation, instDict);

      implementation = new FunctionLiteral(loc, funName, dictType, reqVars, implementation, iFree);
      return new VarEntry(loc, new Variable(loc, dictType, funName), implementation, AccessMode.readOnly, visibility);
    } else {

      Variable[] freeVars = FreeVariables.findFreeVars(implementation, dict);
      implementation = new MemoExp(loc, implementation, freeVars);
      return new VarEntry(loc, new Variable(loc, dictType, funName), implementation, AccessMode.readOnly, visibility);
    }
  }

  public static String instanceFunName(IType type)
  {
    StringBuilder bldr = new StringBuilder();
    bldr.append(type.typeLabel());
    bldr.append("#");
    String sep = "";
    for (IType arg : TypeUtils.typeArgs(type)) {
      arg = TypeUtils.deRef(arg);
      if (!TypeUtils.isDetermines(arg)) {
        bldr.append(sep);
        IType arg1 = arg;
        arg1 = TypeUtils.deRef(arg1);
        if (TypeUtils.isTypeVar(arg1))
          bldr.append("_");
        else
          bldr.append(arg1.typeLabel());
        sep = "_";
      }
    }
    return bldr.toString();
  }

  public static String instanceFunName(String abTypeName, String typeLabel)
  {
    return "@" + abTypeName + "#" + typeLabel;
  }

  public static String instanceFunName(IAbstract tp)
  {
    if (Abstract.isBinary(tp, StandardNames.WHERE))
      return instanceFunName(Abstract.binaryLhs(tp));
    else if (Abstract.isParenTerm(tp))
      return instanceFunName(Abstract.deParen(tp));
    else {
      StringBuilder bldr = new StringBuilder();

      if (Abstract.isBinary(tp, StandardNames.OVER) || Abstract.isBinary(tp, StandardNames.OF)) {
        IAbstract con = Abstract.deParen(Abstract.binaryLhs(tp));
        IAbstract args = Abstract.binaryRhs(tp);

        if (con instanceof Name)
          bldr.append(TypeContracts.contractImplTypeName(Abstract.getId(con)));
        bldr.append("#");

        if (Abstract.isTupleTerm(args) && !Abstract.isParenTerm(args)) {
          String sep = "";
          for (IValue a : ((Apply) args).getArgs()) {
            bldr.append(sep);
            sep = "_";
            instFunArgName((IAbstract) a, bldr);
          }
        } else
          instFunArgName(args, bldr);
      }

      return bldr.toString();
    }
  }

  private static void instFunArgName(IAbstract arg, StringBuilder bldr)
  {
    arg = Abstract.deParen(arg);
    if (Abstract.isBinary(arg, StandardNames.WHERE))
      instFunArgName(Abstract.binaryLhs(arg), bldr);
    else if (Abstract.isIdentifier(arg))
      bldr.append(Abstract.getId(arg));
    else if (Abstract.isBinary(arg, StandardNames.OF) && Abstract.isIdentifier(Abstract.binaryLhs(arg)))
      bldr.append(Abstract.getId(Abstract.binaryLhs(arg)));
    else if (Abstract.isTupleTerm(arg))
      bldr.append(TypeUtils.tupleLabel(Abstract.tupleArity(arg)));
    else if (CompilerUtils.isAnonAggConLiteral(arg))
      bldr.append(CompilerUtils.anonRecordTypeLabel(arg));
    else
      bldr.append("_");
  }

  public static IType computeDictionaryType(IType conType, Location loc, AccessMode access)
  {
    List<TypeVar> uniVars = new ArrayList<TypeVar>();
    IType unwrapped = TypeUtils.unwrap(conType, uniVars);
    List<IType> reqVars = new ArrayList<IType>();

    findTypeContracts(reqVars, uniVars);

    Map<String, TypeVar> boundMap = new HashMap<String, TypeVar>();

    for (TypeVar tVar : uniVars)
      boundMap.put(tVar.typeLabel(), new TypeVar(tVar.getVarName(), tVar.getOriginalName(), access));

    List<IType> requires = new ArrayList<IType>();

    for (TypeVar tVar : uniVars) {
      TypeVar repl = (TypeVar) boundMap.get(tVar.typeLabel());

      for (ITypeConstraint con : (TypeVar) tVar) {
        if (con instanceof FieldConstraint) {
          FieldConstraint recCon = (FieldConstraint) con;
          TypeUtils.setFieldConstraint(repl, loc, recCon.getField(), Refresher.rewrite(recCon.getType(), boundMap));
        } else if (con instanceof FieldTypeConstraint) {
          FieldTypeConstraint recCon = (FieldTypeConstraint) con;
          TypeUtils.setTypeConstraint(repl, recCon.getName(), Refresher.rewrite(recCon.getType(), boundMap));
        }
      }
    }

    for (IType req : reqVars)
      requires.add(Refresher.rewrite(req, boundMap));

    return UniversalType.univ(boundMap.values(), TypeUtils.overloadedType(requires, Refresher.rewrite(unwrapped,
        boundMap)));
  }

  private static void findTypeContracts(List<IType> reqVars, Collection<TypeVar> typeVars)
  {
    for (TypeVar tv : typeVars) {
      reqLoop: for (ITypeConstraint con : tv) {
        if (con instanceof ContractConstraint) {
          TypeExp req = ((ContractConstraint) con).getContract();
          for (IType conType : reqVars) {
            if (conType.equals(req))
              continue reqLoop;
          }

          reqVars.add(req);
        }
      }
    }
  }

  public int substitutionState()
  {
    return substitutions.undoState();
  }

  public void undoSubstitutionState(int mark)
  {
    substitutions.undo(mark);
  }

  protected IContentExpression defineSubstitution(String orig, IContentExpression substitute)
  {
    if (substitute == null)
      return substitutions.remove(orig);
    else
      return substitutions.put(orig, substitute);
  }

  protected IContentExpression substitute(String orig)
  {
    return substitutions.get(orig);
  }

  protected int markSubstitutions()
  {
    return substitutions.size();
  }

}
